﻿using System.Collections.Generic;
using Godot.core;
using Godot.core.math;
using Godot.core.os;
using Godot.scene.resource;

namespace Godot.scene.main
{
    public partial class SceneMainLoop : MainLoop 
    {
        internal class __Group 
        {
		    public List<Node> nodes;
		    public ulong last_tree_version;		
		    
            public __Group() 
            {
                nodes = new List<Node>();
                last_tree_version = 0; 
            }
	    };

        public enum CallGroupFlags
        {
            GROUP_CALL_DEFAULT = 0,
            GROUP_CALL_REVERSE = 1,
            GROUP_CALL_REALTIME = 2,
            GROUP_CALL_UNIQUE = 4,
            GROUP_CALL_MULIILEVEL = 8,
        }

        public override void init()
        {
            __accept_quit = true;
            __is_initialized = true;
            __input_handled = false;

            __editor_hint = false;
            __pause = false;

            __root._set_scene(this);
            base.init();
        }

        public void set_auto_accept_quit(bool p_accept)
        {
            __accept_quit = p_accept;
        }

        public void node_removed(Node p_node) 
        {
	        emit_signal(__node_removed_name, p_node);
	        if (__call_lock > 0)
		        __call_skip.Add(p_node);
        }

        public void tree_changed() 
        {
	        __tree_version++;
	        emit_signal(__tree_changed_name);
        }

        public Viewport get_root()
        {
            throw new System.NotImplementedException();
        }

        public float get_fixed_process_time()
        { 
            return __fixed_process_time; 
        }

	    public float get_idle_process_time()
        { 
            return __idle_process_time; 
        }

        public void add_to_group(StringName p_group, Node p_node)
        {	   
            if (!__group_map.ContainsKey(p_group)) {
		        __group_map.Add(p_group,new __Group());
	        }

	        if (__group_map[p_group].nodes.Contains(p_node)) 
            {
		        //ERR_EXPLAIN("Already in group: "+p_group);
		        return;
	        }

            __group_map[p_group].nodes.Add(p_node);
            __group_map[p_group].last_tree_version = 0;
        }

        public void remove_from_group(StringName p_group, Node p_node) 
        {
            if(!__group_map.ContainsKey(p_group))
                return;

	        __group_map[p_group].nodes.Remove(p_node);
            if (__group_map[p_group].nodes.Count == 0)
                __group_map.Remove(p_group);
        }

        public void call_group(uint p_call_flags, StringName p_group, StringName p_function,
            Variant p_arg1 = null, Variant p_arg2 = null, Variant p_arg3 = null, Variant p_arg4 = null, Variant p_arg5 = null)
        {
        }

        public override bool iteration(float p_time)
        {
            __root_lock++;

	        __current_frame++;

	        __flush_transform_notifications();

	        base.iteration(p_time);

	        __fixed_process_time = p_time;
	        
            __notify_group_pause("fixed_process", Node.NOTIFICATION_FIXED_PROCESS);
	        
            __flush_ugc();
	        
            __flush_transform_notifications();
	        
            call_group((uint)CallGroupFlags.GROUP_CALL_REALTIME,"_viewports","update_worlds");
	        
            __root_lock--;

	        __flush_delete_queue();

	        return __quit;
        }

        public override bool idle(float p_time)
        {
        //	print_line("ram: "+itos(OS::get_singleton()->get_static_memory_usage())+" sram: "+itos(OS::get_singleton()->get_dynamic_memory_usage()));
        //	print_line("node count: "+itos(get_node_count()));
        //	print_line("TEXTURE RAM: "+itos(VS::get_singleton()->get_render_info(VS::INFO_TEXTURE_MEM_USED)));

	        __root_lock++;

	        base.idle(p_time);

	        __idle_process_time = p_time;

	        __flush_transform_notifications();

	        __notify_group_pause("idle_process", Node.NOTIFICATION_PROCESS);

	        Vector2 win_size = new Vector2( OS.singleton.get_video_mode().width, OS.singleton.get_video_mode().height );
	        if(win_size != __last_screen_size) 
            {
		        __last_screen_size = win_size;
		        __root.set_rect(new Rect2(new Vector2(), __last_screen_size));
		        emit_signal("screen_resized");
	        }

	        __flush_ugc();
	        __flush_transform_notifications(); //transforms after world update, to avoid unnecessary enter/exit notifications
	        call_group((uint)CallGroupFlags.GROUP_CALL_REALTIME, "_viewports", "update_worlds");

	        __root_lock--;

	        __flush_delete_queue();

	        return __quit; 
        }

        private void __notify_group_pause(StringName p_group, int p_notification)
        {

        }

        private void __flush_transform_notifications()
        {

        }

        private void __flush_ugc()
        {

        }

        private void __flush_delete_queue()
        {

        }

        public SceneMainLoop()
        {
            __quit = false;
	        __is_initialized = false;
	        __tree_version = 1;
	        __fixed_process_time = 1;
	        __idle_process_time = 1;
	        __last_id = 0;
	        __root = null;
	        __current_frame = 0;
	        __tree_changed_name = "tree_changed";
	        __node_removed_name = "node_removed";
	        __ugc_locked = false;
	        __call_lock = 0;
	        __root_lock = 0;
	        __node_count = 0;

	        //create with mainloop

	        __root = Misc.memnew(new Viewport());
	        __root.set_name("root");
	        __root.set_world( Misc.memnew( new World() ));
	        //root->set_world_2d( Ref<World2D>( memnew( World2D )));
	        __root.set_as_audio_listener(true);
	        __root.set_as_audio_listener_2d(true);

	        __last_screen_size = new Vector2( OS.singleton.get_video_mode().width, OS.singleton.get_video_mode().height );
	        __root.set_rect(new Rect2(new Vector2(), __last_screen_size));

	        //if (ScriptDebugger::get_singleton()) {
		    //    ScriptDebugger::get_singleton()->set_request_scene_tree_message_func(_debugger_request_tree,this);
	        //}

        }

        protected static new void _bind_methods()
        {

        }

        internal ulong __tree_version;
        internal int __root_lock;
        internal int __call_lock;
        internal float __fixed_process_time;
	    internal float __idle_process_time;
        internal SortedSet<Node> __call_skip;
        internal Dictionary<StringName, __Group> __group_map;
        internal StringName __tree_changed_name;
        internal StringName __node_removed_name;
        internal int __node_count;

        private Vector2 __last_screen_size;
        private Viewport __root;
        private bool __quit;
        private bool __editor_hint;
        private bool __pause;
        private bool __input_handled;
        private bool __is_initialized;
        private bool __accept_quit;
        private uint __last_id;
        private long __current_frame;
        private bool __ugc_locked;
        public const int NOTIFICATION_TRANSFORM_CHANGED = 29;
    }

    public partial class SceneMainLoop
    {
        #region OBJ_TYPE( SceneMainLoop, MainLoop )
        /* TYPE API */
        public override string get_type() { return "SceneMainLoop"; }

        public override string get_save_type() { return get_type(); } //type stored when saving

        public override StringName get_type_name() { return (StringName)get_type_static(); }

        public override bool is_type(string p_type) { return (p_type == "SceneMainLoop") ? true : base.is_type(p_type); }

        protected override void _initialize_typev() { initialize_type(); }

        protected override bool _setv(StringName p_name, Variant p_property) 
        { 
            if (base._setv(p_name,p_property)) 
                return true;
	        
            if (typeof(SceneMainLoop).FindMethod("_set") != typeof(MainLoop).FindMethod("_set")) {
		        return _set(p_name,p_property);	
	        }
	        return false; 
        }

        protected override bool _getv(StringName p_name, ref Variant r_property) 
        { 
            if (typeof(SceneMainLoop).FindMethod("_get") != typeof(MainLoop).FindMethod("_get")) 
            {
		        if (_get(p_name, ref r_property))
			        return true;
	        }

	        return base._getv(p_name, ref r_property);    
        }

        protected override void _get_property_listv(ref List<PropertyInfo> p_list, bool p_reversed) 
        {
            if (!p_reversed) 
            {
		        base._get_property_listv(ref p_list, p_reversed);
	        }

	        p_list.Add(new PropertyInfo(Variant.Type.NIL, get_type_static(), PropertyHint.PROPERTY_HINT_NONE, "",(int) PropertyUsageFlags.PROPERTY_USAGE_CATEGORY));
	        
            if (!_is_gpl_reversed())
		        ObjectTypeDB.get_property_list("SceneMainLoop",ref p_list, true);

            if (typeof(SceneMainLoop).FindMethod("_get_property_list") != typeof(MainLoop).FindMethod("_get_property_list")) 
            {
                _get_property_list(ref p_list);
            }

            if (_is_gpl_reversed())
                ObjectTypeDB.get_property_list("SceneMainLoop",ref p_list, true);
            
            if (p_reversed) {
                base._get_property_listv(ref p_list, p_reversed);
            }
        }

        protected override void _notificationv(int p_notification, bool p_reversed) 
        {
            if (!p_reversed) 
		        base._notificationv(p_notification, p_reversed);

	        if (typeof(SceneMainLoop).FindMethod("_notification") != typeof(MainLoop).FindMethod("_notification")) 
            {
		        _notification(p_notification);
	        }

	        if (p_reversed)
		        base._notificationv(p_notification,p_reversed);
        }

        public static new string get_type_static() { return "SceneMainLoop"; }

        public static new string get_parent_type_static() { return MainLoop.get_type_static(); }

        public static new void get_inheritance_list_static(ref List<string> r_inheritance_list) 
        { 
            MainLoop.get_inheritance_list_static(ref r_inheritance_list);
            r_inheritance_list.Add(get_type_static());
        }

        public static new string get_category_static() 
        { 
            string category = MainLoop.get_category_static();
            if (typeof(SceneMainLoop).FindMethod("_get_category") != typeof(MainLoop).FindMethod("_get_category"))
            {
		        if (category != "")
			        category += "/";
		        category += _get_category();
	        }
	        return category; 
        }

        public static new string inherits_static() { return "MainLoop"; }

        public static new void get_valid_parents_static(ref List<string> p_parents) 
        {  
	        if (typeof(SceneMainLoop).FindMethod("_get_valid_parents_static") != typeof(MainLoop).FindMethod("_get_valid_parents_static")) 
            {	
		        SceneMainLoop._get_valid_parents_static(ref p_parents);
	        }

	        MainLoop.get_valid_parents_static(ref p_parents);
        }

        public static new void initialize_type()
        {
            if (__initialized)
                return;
            MainLoop.initialize_type();
            ObjectTypeDB._add_type<SceneMainLoop>();
            if (typeof(SceneMainLoop).FindMethod("_bind_methods") != typeof(MainLoop).FindMethod("_bind_methods"))
                _bind_methods();
            __initialized = true;

        }

        private static bool __initialized = false;
        #endregion
    }
}
